package com.mapbased.wfw.bind;

import com.mapbased.wfw.*;
import java.lang.reflect.*;
import com.mapbased.wfw.components.ShowInfo;

/**
 * <p>Title: </p>
 *
 * <p>Description: </p>
 *
 * <p>Copyright: Copyright (c) 2004</p>
 *
 * <p>Company: mapbased.com</p>
 *
 * @author chy(Baily)
 * @version 1.0
 */
public abstract class AbstractBinding
    implements Binding
{
    /**
     * in {bindingname.attributeName.attributeName} format
     * or {bingname.method(stringparam).attribute.othermethod(dddd)}
     *
     *
     */

    static protected class MethodItem
    {
        Method method;
        String name;
        Object[] param=null;
        Object getValue(Object o)
            throws SecurityException,SecurityException,NoSuchMethodException,
            InvocationTargetException,IllegalArgumentException,
            IllegalAccessException
        {
            if(this.method == null)
            {
                synchronized(this)
                {
                    Class c=o.getClass();

                    String mn=null;

                    if(this.name.indexOf('(') > 0)
                    {
                        Class[] cparam=null;
                        mn=this.name.substring(0,this.name.indexOf('(')).trim();
                        cparam=new Class[]
                            {
                            Object.class};
                        String tmppars=this.name.substring(name.indexOf('(') +
                            1,
                            name.indexOf(')'));
                        if(tmppars.equals(""))
                        {
                            param=new Object[]
                                {};
                        }
                        else
                        {
                            String[] tss=tmppars.split(",");
                            param=new Object[tss.length];
                            System.arraycopy(tss,0,param,0,param.length);

                        }

//

                        Method methods[]=c.getMethods();

                        for(int i=0;i < methods.length;i++)
                        {
                            Method m=methods[i];
                            Class[] tmpps=m.getParameterTypes();
                            if((m.getName().equals(mn) &&
                                tmpps.length == param.length))
                            {
                                // System.out.println("methodname:"+m.getName());
                                // System.out.println("methodname:"+m.getName());

                                /////////////here a lot of thing to do to support type cast//////////////////////////////
                                boolean b=true;
                                for(int mj=0;mj < tmpps.length;mj++)
                                {
                                    if(!tmpps[mj].isAssignableFrom(String.class))
                                    {
                                        b=false;
                                        break;
                                    }
                                }
                                if(b)
                                {
                                    this.method=m;
                                    break;
                                }

                            }

                        }

                        if(this.method == null)
                        {
                            if(this.param.length == 1)
                            {
                                try
                                {
                                    this.method=c.getMethod(mn,new Class[]
                                        {int.class});
                                    this.param[0]=new Integer(String.valueOf(this.
                                        param[0]));
                                }
                                catch(Exception eee)
                                {
                                    this.method=null;
                                }
                            }

                        }

                        if(this.method == null)
                        {
                            for(int i=0;i < methods.length;i++)
                            {
                                System.out.println(methods[i].getName());
                            }
                            throw new java.lang.NoSuchMethodException("Class:" +
                                c + "\n Method:" + mn);
                        }

                    }
                    else
                    {
                        char[] cs=null;
                        cs=this.name.toCharArray();
                        cs[0]=Character.toUpperCase(cs[0]);
                        mn="get" + new String(cs);

                        try
                        {
                            this.method=c.getMethod(mn,null);

                        }

                        catch(NoSuchMethodException ex)
                        {
                            try
                            {
                                this.method=c.getMethod("is" + cs,null);
                            }
                            catch(NoSuchMethodException ex1)
                            {
                                this.method=c.getMethod(this.name,null);

                            }
                        }
                    }
                    if(!this.method.isAccessible())
                    {
                        this.method.setAccessible(true);
                    }
                }

            }

            return this.method.invoke(o,this.param);

        }

        /**
         * Returns a string representation of the object.
         *
         * @return a string representation of the object.
         * @todo Implement this java.lang.Object method
         */
        public String toString()
        {
            return this.name + String.valueOf(this.method);
        }

    }

    protected String attributeName;
    protected String exp;
    MethodItem[] methodItems;
    private Cache cache;
    public AbstractBinding(String exp)

    {
        String tmp=exp.trim();
        this.exp=tmp;
        this.initCache();
        this.initMethods(this.exp);
    }

    /**
     * init a method chaine to call along
     * @param s String
     */
    protected void initMethods(String s)
    {
        String[] ss=s.split("\\.");

        this.attributeName=ss[0];
        methodItems=new MethodItem[ss.length - 1];
        for(int i=1;i < ss.length;i++)
        {
            MethodItem mi=new MethodItem();
            mi.name=ss[i].trim();
            methodItems[i - 1]=mi;
        }

    }

    protected void initCache()
    {
        String tmp=this.exp;
        if(tmp.startsWith("("))
        {
            int eidx=tmp.indexOf(')');
            if(eidx < 0)
            {
                throw new java.lang.RuntimeException("Error parse binfing:" +
                    exp);
            }
            String scz=tmp.substring(1,eidx).trim();
            this.exp=tmp.substring(eidx + 1);

            if(scz.equals(""))
            {
                scz="0";
            }
            int interval=0;
            try
            {
                interval=Integer.parseInt(scz);
            }
            catch(NumberFormatException ex)
            {
                if(scz.endsWith("s"))
                {
                    interval=Integer.parseInt(scz.substring(0,scz.length() - 1));
                }
                else if(scz.endsWith("m"))
                {
                    interval=Integer.parseInt(scz.substring(0,scz.length() - 1)) *
                        60;
                }
                else if(scz.endsWith("h"))
                {
                    interval=Integer.parseInt(scz.substring(0,scz.length() - 1)) *
                        60 * 60;
                }

                else if(scz.endsWith("d"))
                {
                    interval=Integer.parseInt(scz.substring(0,scz.length() - 1)) *
                        60 * 60 * 24;
                }

            }
            if(interval > 0)
            {
                DynamicCache c=new DynamicCache();
                c.fetchInterval=interval * 1000;
                this.cache=c;

            }
            else if(interval == 0)
            {
                this.cache=new StaticCache();
            }
            else
            {
                this.cache=new Cache();
            }

        }
        else
        {
            this.cache=new Cache();
        }

    }

    abstract protected Object getDirectValue(RenderContext context);

    public final Object getValue(RenderContext rc)
    {
//        try
//        {
            return this.cache.getValue(rc);
//        }
//        catch( Exception ex)
//        {
//             StringBuilder sb=new StringBuilder();
//            java.util.Map m=ShowInfo.requestMap(rc);
//            for(java.lang.Object e:m.entrySet())
//            {
//               sb.append(e.toString()).append("\n");
//            }
//            throw new RuntimeException(sb.toString(),ex);
//        }
    }

    final Object getChainValue(RenderContext context)
    {
        Object o=this.getDirectValue(context);
        if(o == null)
        {
            return null;
        }
        for(int i=0;i < this.methodItems.length;i++)
        {
            try
            {
                o=this.methodItems[i].getValue(o);
                if(o == null)
                {
                    return null;
                }
            }
            catch(Exception ex)
            {
                throw new java.lang.RuntimeException(
                    "Error while getting value from binding:\n"
                    + ex.toString() + "\n" + this.methodItems[i] + "--" +
                    this.exp + "--" + o,ex);
            }
        }
        return o;

    }

    public String toString()
    {
        return "@abstract{" + exp + "}";
    }

    public static void main(String s[])
    {
        long l=System.currentTimeMillis();
        for(int i=0;i < 1000000;i++)
        {
            new java.util.HashMap();
        }
        System.out.println(System.currentTimeMillis() - l);
        /*  String ss[]="/com/chy/test/sdsdsd.html?sds=sdsd/sdsds/dsf".split("/");
          for(int i=0;i < ss.length;i++)
          {
              System.out.println("[" + ss[i] + "]");
          }*/
    }

    class Cache
    {

        Object getValue(RenderContext ctx)
        {
            return getChainValue(ctx);
        }
    }

    class StaticCache
        extends Cache
    {
        boolean fetched=false;
        Object cachedValue;

        synchronized Object getValue(RenderContext ctx)
        {
            if(!fetched)
            {

                cachedValue=getChainValue(ctx);
                fetched=true;

            }

            return cachedValue;
        }

    }

    class DynamicCache
        extends StaticCache
    {
        long lastFetchTime;
        int fetchInterval;
        boolean fetching=false;

        synchronized Object getValue(final RenderContext ctx)
        {
            if(!fetched)
            {

                cachedValue=getChainValue(ctx);
                fetched=true;
                lastFetchTime=System.currentTimeMillis();
                return cachedValue;

            }

            if(System.currentTimeMillis() - lastFetchTime > fetchInterval &&
               !this.fetching)
            {
                fetching=true;
                final Object self=this;
                new Thread()
                {
                    public void run()
                    {
                        Object o=null;

                        try
                        {
                            o=getChainValue(ctx);
                        }
                        finally
                        {

                            synchronized(self)
                            {
                                cachedValue=o;
                                lastFetchTime=System.currentTimeMillis();
                                fetching=false;
                            }
                        }

                    }
                }.start();

            }

            return cachedValue;
        }

    }

}
